package com.cn.server.websocket;

import com.cn.common.core.FastJsonUtils;
import com.cn.common.core.model.*;
import com.cn.common.core.session.Session;
import com.cn.common.core.session.SessionImpl;
import com.cn.common.core.session.SessionManager;
import com.cn.server.module.login.ImUser;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.AttributeKey;
import io.netty.util.CharsetUtil;

import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

/**
 */
public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {
    /**
     * 设置type 类型,网页websocket
     */
    private AttributeKey<String> mAttributeKey = AttributeKey.valueOf("type");
    private static final Logger LOGGER = Logger.getLogger(WebSocketServerHandshaker.class.getName());
    private WebSocketServerHandshaker handshaker;

    private WebSocketMessageDispatcher webSocketMessageDispatcher;

    public WebSocketServerHandler(WebSocketMessageDispatcher webSocketMessageDispatcher) {
        this.webSocketMessageDispatcher = webSocketMessageDispatcher;
    }

    /**
     * channel 通道 Inactive 不活跃的 当客户端主动断开服务端的链接后，这个通道就是不活跃的。也就是说客户端与服务端关闭了通信通道并且不可以传输数据
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Session session = new SessionImpl(ctx.channel());
        Object object = session.getAttachment();
        if (object != null) {
            ImUser imUser = (ImUser) object;
            SessionManager.removeSession(imUser.getLoginName());
        }
    }

    /**
     * 接收客户端发送的消息 channel 通道 Read 读 简而言之就是从通道中读取数据，也就是服务端接收客户端发来的数据。但是这个数据在不进行解码时它是ByteBuf类型的
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof FullHttpRequest) {
            // 传统的HTTP接入
            handleHttpRequest(ctx, ((FullHttpRequest) msg));
        } else if (msg instanceof WebSocketFrame) {
            // WebSocket接入
            handlerWebSocketFrame(ctx, (WebSocketFrame) msg);
        }
    }


    private void handlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
        // 判断是否关闭链路的指令
        if (frame instanceof CloseWebSocketFrame) {
            Object attachment = new SessionImpl(ctx.channel()).getAttachment();
            if (attachment != null) {
                ImUser imUser = (ImUser) attachment;
                SessionManager.removeSession(imUser.getLoginName());
            }
            handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            return;
        }
        // 判断是否ping消息
        if (frame instanceof PingWebSocketFrame) {
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        // 本例程仅支持文本消息，不支持二进制消息
        if (!(frame instanceof TextWebSocketFrame)) {
            System.out.println("本例程仅支持文本消息，不支持二进制消息");
            throw new UnsupportedOperationException(
                    String.format("%s frame types not supported", frame.getClass().getName()));
        }
        // 返回应答消息
        String json = ((TextWebSocketFrame) frame).text();
        handlerMessage(new SessionImpl(ctx.channel()), json);
    }

    private void handlerMessage(SessionImpl session, String json) {
        WebSockRequest webSockRequest = FastJsonUtils.fromJson(json, WebSockRequest.class);
        Request request = new Request();
        request.setModule(webSockRequest.getModule());
        request.setCmd(webSockRequest.getCmd());
        request.setData(webSockRequest.getData().getBytes());
        session.setType(SessionImpl.Type.WEBSOCKET);
        webSocketMessageDispatcher.onReceive(request, session);
    }

    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
        // 如果HTTP解码失败，返回HHTP异常
        if (!req.getDecoderResult().isSuccess() || (!"websocket".equals(req.headers().get("Upgrade")))) {
            sendHttpResponse(ctx, req,
                    new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }
        //获取url后置参数
        HttpMethod method = req.getMethod();
        String uri = req.getUri();
        QueryStringDecoder queryStringDecoder = new QueryStringDecoder(uri);
        Map<String, List<String>> parameters = queryStringDecoder.parameters();
        if (!parameters.isEmpty()) {
            System.out.println(parameters.get("request").get(0));
        }
        if (method == HttpMethod.GET && "/websocket".equals(uri)) {
            //...处理
            ctx.attr(mAttributeKey).set("h5");
        }
        // 构造握手响应返回，本机测试
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
                "ws://" + req.headers().get(HttpHeaders.Names.HOST) + uri, null, false);
        handshaker = wsFactory.newHandshaker(req);
        if (handshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel());
        } else {
            handshaker.handshake(ctx.channel(), req);
        }
    }

    private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, DefaultFullHttpResponse res) {
        // 返回应答给客户端
        if (res.getStatus().code() != 200) {
            ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
        }
        // 如果是非Keep-Alive，关闭连接
        ChannelFuture f = ctx.channel().writeAndFlush(res);
        if (!HttpHeaders.isKeepAlive(req) || res.getStatus().code() != 200) {
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }
}